<?php
/**
 * Created by PhpStorm.
 * User: ownfi
 * Date: 2017-03-29
 * Time: 12:42
 */

namespace App\Services\Impl;


use App\Common\Constant\Params;
use App\Common\Constant\Status;
use App\Models\HistoryMasterBalance;
use App\Models\HistoryMasterFund;
use App\Models\MasterCash;
use App\Models\MasterFund;
use App\Models\MasterTradeLog;
use App\Models\MasterWallet;
use App\Models\Order;
use App\Services\IMasterLogService;
use App\Services\IMasterService;
use App\Services\IPayService;
use App\Services\ISystemService;
use App\Services\IWalletService;
use Carbon\Carbon;
use Crypt;
use DB;
use Log;

class WalletServiceImpl implements IWalletService
{

    private $systemService;
    private $masterLogService;

    public function __construct(ISystemService $systemService,IMasterLogService $masterLogService)
    {
        $this->systemService = $systemService;
        $this->masterLogService = $masterLogService;
    }

    public function getWalletByUserid($master_id)
    {
        return MasterWallet::firstOrCreate(['master_id'=>$master_id]);
    }

    public function issetPassword($master_id)
    {
        return !empty(MasterWallet::where(['master_id'=>$master_id])->first()->wallet_password);
    }

    public function changePassword($master_id, $password)
    {
        return MasterWallet::where(['master_id'=>$master_id])->take(1)->update([
            'wallet_password' => Crypt::encryptString($password)
        ]);
    }

    public function verifyPassword($master_id, $password)
    {
        $model = MasterWallet::where(['master_id'=>$master_id])->first();
        if (!$model){
            Log::error(sprintf('%s/%s::%s[%d] %s',__NAMESPACE__,__CLASS__,__FUNCTION__,__LINE__-2,'miss master wallet'));
            return false;
        }
        return $password == Crypt::decryptString($model->wallet_password);
    }

    public function issetAlipayAccount($master_id)
    {
        $model = MasterWallet::where(['master_id'=>$master_id])->first();
        return !empty($model->alipay_name) && !empty($model->alipay_account);
    }

    public function alipayAccountBind($master_id, $alipay_name, $alipay_account)
    {
        return MasterWallet::where(['master_id'=>$master_id])->take(1)->update([
            'alipay_name' => $alipay_name,
            'alipay_account' => $alipay_account,
        ]);
    }

    public function applyCash(MasterWallet $masterWallet, $amount)
    {
        $cash = MasterCash::where('master_id',$masterWallet->master_id)->where('add_time','>',Carbon::now()->subDays(config('common.cash_once_days'))->timestamp)->count();
        if ($cash){
            return Status::CASH_ONCE_IN_DAYS;
        }

        if ($masterWallet->balance < $amount){//余额不足以支付
            Log::error(sprintf('%s/%s::%s[%d] %s',__NAMESPACE__,__CLASS__,__FUNCTION__,__LINE__-2,'balance not enough'));
            return Status::PAYMENT_WALLET_BALANCE_NOT_ENOUGH;
        }

        $cashOrder =  MasterCash::create([
            'order_number'=>$this->systemService->genInsideOrderNumber(),
            'master_id'=>$masterWallet->master_id,
            'amount'=>$amount,
            'alipay_name'=>$masterWallet->alipay_name,
            'alipay_account'=>$masterWallet->alipay_account,
            'last_balance'=>$masterWallet->balance,
            'status'=>Params::MASTER_CASH_REVIEW,
        ]);
        if (!$cashOrder){
            Log::error(sprintf('%s/%s::%s[%d] %s',__NAMESPACE__,__CLASS__,__FUNCTION__,__LINE__-2,'cash create fail'));
            return false;
        }

        $trade_log = $this->masterLogService->cashLog($cashOrder);
        if (!$trade_log){
            Log::error(sprintf('%s/%s::%s[%d] %s',__NAMESPACE__,__CLASS__,__FUNCTION__,__LINE__-2,'cash trade log create fail'));
            return false;
        }

        $ret = $this->subtractBalance($trade_log->id,$masterWallet->master_id,$amount);

        return $ret ? Status::CASH_OK : false;
    }

    public function getCashingAmount($master_id)
    {
        return MasterCash::where(['master_id'=>$master_id,'status'=>Params::MASTER_CASH_REVIEW])->sum('amount');
    }

    public function genAssureFundOrder($master_id, $amount)
    {
        return MasterFund::create([
            'order_number'=>$this->systemService->genInsideOrderNumber(),
            'master_id'=>$master_id,
            'amount'=>$amount,
            'type'=>Params::MASTER_FUND_TYPE_PAY,
            'status'=>Params::ORDER_STATUS_UNPAID,
        ]);
    }

    public function pay($trade_number)
    {
        $trade_log = MasterTradeLog::where('trade_number',$trade_number)->first();
        if (!$trade_log){
            Log::error(sprintf('%s/%s::%s[%d] %s',__NAMESPACE__,__CLASS__,__FUNCTION__,__LINE__-2,'miss trade log'));
            return Status::PAYMENT_FAIL;
        }

        $master_id = getCurrentUserid();
        $wallet = MasterWallet::where(['master_id'=>$master_id])->first();

        if ($wallet->balance < $trade_log->amount){//余额不足以支付
            Log::error(sprintf('%s/%s::%s[%d] %s',__NAMESPACE__,__CLASS__,__FUNCTION__,__LINE__-2,'balance not enough'));
            return Status::PAYMENT_WALLET_BALANCE_NOT_ENOUGH;
        }

        DB::beginTransaction();
        try{

            //扣除余额
            $ret = $this->subtractBalance($trade_log->id,$master_id,$trade_log->amount);
            if (!$ret){
                Log::error(sprintf('%s/%s::%s[%d] %s',__NAMESPACE__,__CLASS__,__FUNCTION__,__LINE__-2,'subtract fail'));
                return false;
            }

            $trade_log->status = Params::TRADE_STATUS_SUCCESS;
            $payService = resolve(IPayService::class);
            $payService->setPaymentMethod(Params::PAY_METHOD_BALANCE);
            $ret = $trade_log->save() && $payService->handlePaymentSuccess($trade_number);

            if ($ret){
                DB::commit();
            }else{
                Log::error(sprintf('%s/%s::%s[%d] %s',__NAMESPACE__,__CLASS__,__FUNCTION__,__LINE__-2,'pay fail'));
                DB::rollBack();
            }
            return $ret ? Status::PAYMENT_SUCCESS : false;
        }catch (\Exception $e){
            DB::rollBack();
            throw $e;
        }

    }

    public function returnAssureFund($master_id)
    {
        $wallet = MasterWallet::where('master_id',$master_id)->first();
        if (!$wallet){
            return false;
        }
        if (!$wallet->assure_fund){
            return false;
        }
        $fundOrder = MasterFund::create([
            'order_number'=>$this->systemService->genInsideOrderNumber(),
            'master_id'=>$master_id,
            'amount'=>$wallet->assure_fund,
            'type'=>Params::MASTER_FUND_TYPE_RETURN,
            'alipay_name'=>$wallet->alipay_name,
            'alipay_account'=>$wallet->alipay_account,
            'status'=>Params::MASTER_FUND_WAITING,
        ]);
        if (!$fundOrder){
            return false;
        }
        $trade_log = $this->masterLogService->returnAssureFundLog($fundOrder);
        if (!$trade_log){
            return false;
        }
        return $this->subtractAssureFund($trade_log->id,$master_id,$wallet->assure_fund);
    }

    public function subtractBalance($trade_id,$master_id,$amount)
    {
        $history = HistoryMasterBalance::where('trade_id',$trade_id)->first();
        if ($history && ($history->direction == Params::TRADE_DIRECTION_OUT)){
            Log::error(sprintf('%s/%s::%s[%d] %s',__NAMESPACE__,__CLASS__,__FUNCTION__,__LINE__-2,'trade history already exists'));
            return false;
        }

        DB::beginTransaction();
        try{
            $amount = floatval($amount);

            $model = MasterWallet::where(['master_id'=>$master_id])->lockForUpdate()->first();
            if (empty($model)){
                Log::error(sprintf('%s/%s::%s[%d] %s',__NAMESPACE__,__CLASS__,__FUNCTION__,__LINE__-2,'miss master wallet'));
                DB::rollBack();
                return false;
            }
            if (empty($model->balance) || ($model->balance < $amount)){
                Log::error(sprintf('%s/%s::%s[%d] %s',__NAMESPACE__,__CLASS__,__FUNCTION__,__LINE__-2,'balance not enough'));
                DB::rollBack();
                return false;
            }
            $current_balance = $model->balance;
            $new_balance = $current_balance - $amount;
            $model->balance = DB::raw("balance - {$amount}");
            $model->last_trade_id = $trade_id;
            $model->last_trade_balance = $current_balance;
            $model->last_trade_time = time();
            $model->last_trade_amount = - $amount;

            $ret = HistoryMasterBalance::create([
                    'trade_id'=>$trade_id,
                    'master_id'=>$master_id,
                    'direction'=>Params::TRADE_DIRECTION_OUT,
                    'amount'=>$amount,
                    'current_balance'=>$current_balance,
                    'new_balance'=>$new_balance,
                ]) && $model->save();

            if ($ret){
                DB::commit();
            }else{
                Log::error(sprintf('%s/%s::%s[%d] %s',__NAMESPACE__,__CLASS__,__FUNCTION__,__LINE__-2,'subtract fail'));
                DB::rollBack();
            }

            return $ret;
        }catch (\Exception $e){
            DB::rollBack();
            throw $e;
        }

    }

    public function raiseBalance($trade_id, $master_id, $amount)
    {
        $history = HistoryMasterBalance::where('trade_id',$trade_id)->first();
        if ($history && ($history->direction == Params::TRADE_DIRECTION_IN)){
            Log::error(sprintf('%s/%s::%s[%d] %s',__NAMESPACE__,__CLASS__,__FUNCTION__,__LINE__-2,'trade history already exists'));
            return false;
        }

        DB::beginTransaction();
        try{
            $amount = floatval($amount);

            $model = MasterWallet::where(['master_id'=>$master_id])->lockForUpdate()->first();
            if (empty($model)){
                Log::error(sprintf('%s/%s::%s[%d] %s',__NAMESPACE__,__CLASS__,__FUNCTION__,__LINE__-2,'miss master wallet'));
                DB::rollBack();
                return false;
            }
            $current_balance = $model->balance;
            $new_balance = $current_balance + $amount;
            $model->balance = DB::raw("balance + {$amount}");
            $model->last_trade_id = $trade_id;
            $model->last_trade_balance = $current_balance;
            $model->last_trade_time = time();
            $model->last_trade_amount = $amount;

            $ret = HistoryMasterBalance::create([
                    'trade_id'=>$trade_id,
                    'master_id'=>$master_id,
                    'direction'=>Params::TRADE_DIRECTION_IN,
                    'amount'=>$amount,
                    'current_balance'=>$current_balance,
                    'new_balance'=>$new_balance,
                ]) && $model->save();

            if ($ret){
                DB::commit();
            }else{
                Log::error(sprintf('%s/%s::%s[%d] %s',__NAMESPACE__,__CLASS__,__FUNCTION__,__LINE__-2,'raise fail'));
                DB::rollBack();
            }

            return $ret;
        }catch (\Exception $e){
            DB::rollBack();
            throw $e;
        }
    }

    public function subtractAssureFund($trade_id, $master_id, $amount)
    {
        $history = HistoryMasterFund::where('trade_id',$trade_id)->first();
        if ($history && ($history->direction == Params::TRADE_DIRECTION_OUT)){
            Log::error(sprintf('%s/%s::%s[%d] %s',__NAMESPACE__,__CLASS__,__FUNCTION__,__LINE__-2,'trade history already exists'));
            return false;
        }

        DB::beginTransaction();
        try{
            $amount = floatval($amount);

            $model = MasterWallet::where(['master_id'=>$master_id])->lockForUpdate()->first();
            if (empty($model)){
                Log::error(sprintf('%s/%s::%s[%d] %s',__NAMESPACE__,__CLASS__,__FUNCTION__,__LINE__-2,'miss master wallet'));
                DB::rollBack();
                return false;
            }
            if (empty($model->assure_fund) || ($model->assure_fund < $amount)){
                Log::error(sprintf('%s/%s::%s[%d] %s',__NAMESPACE__,__CLASS__,__FUNCTION__,__LINE__-2,'assure fund not enough'));
                DB::rollBack();
                return false;
            }
            $current_balance = $model->assure_fund;
            $new_balance = $current_balance - $amount;
            $model->assure_fund = DB::raw("assure_fund - {$amount}");

            $ret = HistoryMasterFund::create([
                    'trade_id'=>$trade_id,
                    'master_id'=>$master_id,
                    'direction'=>Params::TRADE_DIRECTION_OUT,
                    'amount'=>$amount,
                    'current_balance'=>$current_balance,
                    'new_balance'=>$new_balance,
                ]) && $model->save();

            if ($ret){
                DB::commit();
            }else{
                Log::error(sprintf('%s/%s::%s[%d] %s',__NAMESPACE__,__CLASS__,__FUNCTION__,__LINE__-2,'subtract fail'));
                DB::rollBack();
            }

            return $ret;
        }catch (\Exception $e){
            DB::rollBack();
            throw $e;
        }
    }

    public function raiseAssureFund($trade_id, $master_id, $amount)
    {
        $history = HistoryMasterFund::where('trade_id',$trade_id)->first();
        if ($history && ($history->direction == Params::TRADE_DIRECTION_IN)){
            Log::error(sprintf('%s/%s::%s[%d] %s',__NAMESPACE__,__CLASS__,__FUNCTION__,__LINE__-2,'trade history already exists'));
            return false;
        }

        DB::beginTransaction();
        try{
            $amount = floatval($amount);

            $model = MasterWallet::where(['master_id'=>$master_id])->lockForUpdate()->first();
            if (empty($model)){
                Log::error(sprintf('%s/%s::%s[%d] %s',__NAMESPACE__,__CLASS__,__FUNCTION__,__LINE__-2,'miss master wallet'));
                DB::rollBack();
                return false;
            }
            $current_balance = $model->assure_fund;
            $new_balance = $current_balance + $amount;
            $model->assure_fund = DB::raw("assure_fund + {$amount}");

            $ret = HistoryMasterFund::create([
                    'trade_id'=>$trade_id,
                    'master_id'=>$master_id,
                    'direction'=>Params::TRADE_DIRECTION_IN,
                    'amount'=>$amount,
                    'current_balance'=>$current_balance,
                    'new_balance'=>$new_balance,
                ]) && $model->save();

            if ($ret){
                DB::commit();
            }else{
                Log::error(sprintf('%s/%s::%s[%d] %s',__NAMESPACE__,__CLASS__,__FUNCTION__,__LINE__-2,'raise fail'));
                DB::rollBack();
            }

            return $ret;
        }catch (\Exception $e){
            DB::rollBack();
            throw $e;
        }
    }
}